// src/source/source-parser.service.ts
import { Injectable } from '@nestjs/common';
import * as cheerio from 'cheerio';
import axios from '../shared/axios.instance';

@Injectable()
export class SourceParserService {
  /**
   * 辅助方法：从指定元素中根据规则提取数据  
   * 规则格式示例：
   *   - "h1@text" 表示获取 h1 元素的文本  
   *   - "div@href" 表示获取 div 元素的 href 属性  
   *   - "tag.img@src" 表示在当前元素中查找 img 标签，然后取其 src 属性  
   *   - 如果规则以 "text." 开头，则作为固定文本返回  
   */
  private applyRuleFromElement(element: cheerio.Cheerio<any>): (rule: string) => string {
    return (rule: string): string => {
      if (rule.startsWith("text.")) {
        return rule.replace("text.", "").trim();
      }
      const parts = rule.split("@");
      let el = element; // 当前元素作为起始点
      if (parts.length === 1) {
        return el.text().trim();
      } else {
        // 使用前面所有部分（除最后一部分）构造选择器
        const selector = parts.slice(0, parts.length - 1).join(" ");
        el = el.find(selector);
        const last = parts[parts.length - 1].trim();
        if (last === "text") {
          return el.text().trim();
        } else {
          return el.attr(last) ? el.attr(last).trim() : "";
        }
      }
    };
  }

  /**
   * 解析小说的基本信息，使用 ruleBookInfo 规则  
   * ruleBookInfo 对象中每个键对应要提取的字段，值为解析规则字符串  
   * 示例规则：
   *   "name": "class.info@tag.h1@text"  
   *   "author": "class.info@tag.p.0@text##作.*者："  
   */
  parseBookInfo(html: string, ruleBookInfo: any): any {
    const $ = cheerio.load(html);
    const result: any = {};
    for (const key in ruleBookInfo) {
      const ruleString = ruleBookInfo[key];
      // 简单处理：取规则中第一个部分作为选择器，然后获取文本
      const selector = ruleString.split("@")[0];
      result[key] = $(selector).text().trim();
    }
    return result;
  }

  /**
   * 解析小说目录（章节列表），使用 ruleToc 规则  
   * ruleToc 至少应包含：  
   *   - chapterList: 用于选择所有章节的 CSS 选择器  
   *   - chapterName: 指定如何提取章节名称  
   *   - chapterUrl: 指定如何提取章节链接  
   */
  parseToc(html: string, ruleToc: any): any[] {
    const $ = cheerio.load(html);
    const chapters: any[] = [];
    if (ruleToc.chapterList) {
      $(ruleToc.chapterList).each((index, elem) => {
        const el = $(elem);
        const extractor = this.applyRuleFromElement(el);
        const chapterName = ruleToc.chapterName ? extractor(ruleToc.chapterName) : el.text().trim();
        const chapterUrl = ruleToc.chapterUrl ? extractor(ruleToc.chapterUrl) : (el.attr("href") || "");
        chapters.push({ chapterName, chapterUrl });
      });
    }
    return chapters;
  }

  /**
   * 解析章节内容，使用 ruleContent 规则  
   * ruleContent 对象中至少应包含 content 字段，表示正文提取规则  
   */
  parseContent(html: string, ruleContent: any): string {
    const $ = cheerio.load(html);
    return $(ruleContent.content).html()?.trim() || "";
  }

  /**
   * 根据新的书源规则爬取小说数据  
   * 支持解析：
   *   - 小说基本信息（ruleBookInfo）
   *   - 目录（ruleToc）
   *   - 章节内容（ruleContent.content）
   * 书源规则取自书源实体中配置的规则(JSON格式)
   */
  async crawlNovelByRules(
    url: string,
    rules: { ruleBookInfo?: any; ruleToc?: any; ruleContent?: any }
  ): Promise<any> {
    try {
      const response = await axios.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
      const html = response.data;
      let bookInfo = null;
      if (rules.ruleBookInfo) {
        bookInfo = this.parseBookInfo(html, rules.ruleBookInfo);
      }
      let toc = null;
      if (rules.ruleToc) {
        toc = this.parseToc(html, rules.ruleToc);
      }
      let content = null;
      if (rules.ruleContent && rules.ruleContent.content) {
        content = this.parseContent(html, rules.ruleContent);
      }
      return { bookInfo, toc, content };
    } catch (error) {
      throw new Error(`爬取小说数据失败: ${error.message}`);
    }
  }

  /**
 * 解析搜索结果页面，提取小说信息  
 * ruleSearch 规则示例可以是：
 * {
 *    "resultContainer": ".search-result-item",
 *    "name": "tag.h3@text",
 *    "author": "tag.p.author@text",
 *    "detailUrl": "tag.a@href",
 *    "intro": "tag.p.intro@text"
 * }
 */
  parseSearchResults(html: string, ruleSearch: any): any[] {
    const $ = cheerio.load(html);
    const results: any[] = [];
    if (ruleSearch.resultContainer) {
      $(ruleSearch.resultContainer).each((index, elem) => {
        const el = $(elem);
        const extractor = this.applyRuleFromElement(el);
        const name = ruleSearch.name ? extractor(ruleSearch.name) : el.text().trim();
        const author = ruleSearch.author ? extractor(ruleSearch.author) : "";
        const detailUrl = ruleSearch.detailUrl ? extractor(ruleSearch.detailUrl) : el.find('a').attr('href') || "";
        const intro = ruleSearch.intro ? extractor(ruleSearch.intro) : "";
        results.push({ name, author, detailUrl, intro });
      });
    }
    return results;
  }
}